﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Globalization;

namespace Imagenator
{
	static class PatternEngine
	{
		public delegate string Handler(ImageInfo imageInfo);
		static readonly Placeholder[] placeholders = PatternEngine.GetPlaceholders();
		public static Placeholder[] Placeholders { get { return PatternEngine.placeholders; } }

		#region Private Methods
		static object[] GetParameter(ImageInfo imageInfo)
		{
			object[] parameters = new object[PatternEngine.Placeholders.Length];
			for (int i = 0; i < parameters.Length; i++)
				parameters[i] = PatternEngine.Placeholders[i].GetValue(imageInfo);
			return parameters;
		}

		static Placeholder[] GetPlaceholders()
		{
			return new Placeholder[]
			{
				// Year (short)
				new Placeholder("y",
					new ArgusLib.Func<object, ImageInfo>((imageInfo) =>
						{
							int twoDigitYear = imageInfo.Timestamp.Year - 1900;
							while (twoDigitYear > 99)
								twoDigitYear -= 100;
							return twoDigitYear;
						}),
					new ArgusLib.Func<string,string>(PatternEngine.GetFormatInteger),
					new ArgusLib.Func<string>(()=>{return Localization.Text.DescrYearShort;})),
				new Placeholder("Y",
					new ArgusLib.Func<object,ImageInfo>((imageInfo)=>{return imageInfo.Timestamp.Year;}),
					new ArgusLib.Func<string,string>(PatternEngine.GetFormatInteger),
					new ArgusLib.Func<string>(()=>{return Localization.Text.DescrYearLong;})),
				new Placeholder("m",
					new ArgusLib.Func<object,ImageInfo>((imageInfo)=>{return imageInfo.Timestamp.Month;}),
					new ArgusLib.Func<string,string>(PatternEngine.GetFormatInteger),
					new ArgusLib.Func<string>(()=>{return Localization.Text.DescrMonthNumber;})),
				new Placeholder("M", 
					new ArgusLib.Func<object,ImageInfo>((imageInfo)=>
						{return CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedMonthName(imageInfo.Timestamp.Month);}),
					null,
					new ArgusLib.Func<string>(()=>{return Localization.Text.DescrMonthTextShort;})),
				new Placeholder("MM", 
					new ArgusLib.Func<object,ImageInfo>((imageInfo)=>
						{return CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(imageInfo.Timestamp.Month);}),
					null,
					new ArgusLib.Func<string>(()=>{return Localization.Text.DescrMonthTextLong;})),
				new Placeholder("d", 
					new ArgusLib.Func<object,ImageInfo>((imageInfo)=>{return imageInfo.Timestamp.Day;}),
					new ArgusLib.Func<string,string>(PatternEngine.GetFormatInteger),
					new ArgusLib.Func<string>(()=>{return Localization.Text.DescrDayNumber;})),
				new Placeholder("D", 
					new ArgusLib.Func<object,ImageInfo>((imageInfo)=>
						{return CultureInfo.CurrentCulture.DateTimeFormat.GetAbbreviatedDayName(imageInfo.Timestamp.DayOfWeek);}),
					null,
					new ArgusLib.Func<string>(()=>{return Localization.Text.DescrDayTextShort;})),
				new Placeholder("DD", 
					new ArgusLib.Func<object,ImageInfo>((imageInfo)=>
						{return CultureInfo.CurrentCulture.DateTimeFormat.GetDayName(imageInfo.Timestamp.DayOfWeek);}),
					null,
					new ArgusLib.Func<string>(()=>{return Localization.Text.DescrDayTextLong;})),
				new Placeholder("h", 
					new ArgusLib.Func<object,ImageInfo>((imageInfo)=>{return imageInfo.Timestamp.Hour;}),
					new ArgusLib.Func<string,string>(PatternEngine.GetFormatInteger),
					new ArgusLib.Func<string>(()=>{return Localization.Text.DescrHour;})),
				new Placeholder("min", 
					new ArgusLib.Func<object,ImageInfo>((imageInfo)=>{return imageInfo.Timestamp.Minute;}),
					new ArgusLib.Func<string,string>(PatternEngine.GetFormatInteger),
					new ArgusLib.Func<string>(()=>{return Localization.Text.DescrMinute;})),
				new Placeholder("s", 
					new ArgusLib.Func<object,ImageInfo>((imageInfo)=>{return imageInfo.Timestamp.Second;}),
					new ArgusLib.Func<string,string>(PatternEngine.GetFormatInteger),
					new ArgusLib.Func<string>(()=>{return Localization.Text.DescrSecond;})),
				new Placeholder("n", 
					new ArgusLib.Func<object,ImageInfo>((imageInfo)=>{return imageInfo.Number;}),
					new ArgusLib.Func<string,string>(PatternEngine.GetFormatInteger),
					new ArgusLib.Func<string>(()=>{return Localization.Text.DescrNumber;})),
				new Placeholder("fn",
					new ArgusLib.Func<object,ImageInfo>((imageInfo)=>{return new Filename(imageInfo.Filename);}),
					new ArgusLib.Func<string,string>((format)=>{return format;}),
					new ArgusLib.Func<string>(()=>{return Localization.Text.DescrFilename;}))
			};
		}

		static string GetFormatInteger(string format)
		{
			int i;
			if (int.TryParse(format, out i) == false)
				return null;
			return 'd' + i.ToString(NumberFormatInfo.InvariantInfo);
		}
		#endregion

		public static Handler GetHandler(string pattern)
		{
			StringBuilder format = new StringBuilder();
			char[] endChars = new char[]{':','}'};
			int a = 0;
			int b = pattern.IndexOf('{');
			while (b > -1)
			{
				format.Append(pattern.Substring(a, b - a+1));
				a = b+1;
				b = pattern.IndexOfAny(endChars, a);
				if (b < 0)
					return null;

				string identifier = pattern.Substring(a, b - a);

				string fi = null;
				if (pattern[b] == ':')
				{
					int c = pattern.IndexOf('}', b);
					if (c < 0)
						return null;
					fi = pattern.Substring(b + 1, c - b-1);
					b = c;
				}

				bool validFormat = false;
				for (int i = 0; i < PatternEngine.Placeholders.Length; i++)
				{
					if (PatternEngine.Placeholders[i].Identifier != identifier)
						continue;

					if (PatternEngine.Placeholders[i].SupportsFormatting == false && fi != null)
						return null;

					format.Append(i.ToString(NumberFormatInfo.InvariantInfo));
					if (fi != null)
					{
						format.Append(':');
						string temp = PatternEngine.Placeholders[i].GetFormat(fi);
						if (temp == null)
							return null;
						format.Append(temp);
					}
					validFormat = true;
					break;
				}
				if (validFormat == false)
					return null;
				a = b;
				b = pattern.IndexOf('{', a);
			}
			format.Append(pattern.Substring(a));
			string sFormat = format.ToString();

			return new Handler((imageInfo) =>
				{
					object[] parameter = PatternEngine.GetParameter(imageInfo);
					return String.Format(sFormat, parameter);
				});
		}

		public class Placeholder
		{
			public string Identifier { get; private set; }
			public bool SupportsFormatting { get; private set; }
			ArgusLib.Func<object, ImageInfo> getValue;
			ArgusLib.Func<string,string> getFormat;
			ArgusLib.Func<string> getDescription;
			public string Description { get { return this.getDescription(); } }

			public Placeholder(string identifier, ArgusLib.Func<object, ImageInfo> getValue, ArgusLib.Func<string,string> getFormat, ArgusLib.Func<string> getDescription)
			{
				this.Identifier = identifier;
				this.SupportsFormatting = getFormat != null;
				this.getValue = getValue;
				this.getFormat = getFormat;
				this.getDescription = getDescription;object o;
			}

			public object GetValue(ImageInfo imageInfo){return this.getValue(imageInfo);}
			public string GetFormat(string format) { return this.getFormat(format); }
		}

		class Filename : IFormattable
		{
			string filename;

			public Filename(string filename)
			{
				this.filename = filename;
			}

			public string ToString(string format, IFormatProvider formatProvider)
			{
				if (string.IsNullOrEmpty(format) == true)
					return System.IO.Path.GetFileNameWithoutExtension(this.filename);
				string[] f = format.Split(':');
				if (f.Length < 1)
					return System.IO.Path.GetFileNameWithoutExtension(this.filename);
				int level;
				if (int.TryParse(f[0], out level) == false)
					return System.IO.Path.GetFileNameWithoutExtension(this.filename);
				
				int a = this.filename.LastIndexOf(System.IO.Path.DirectorySeparatorChar);
				if (a < 0)
					return System.IO.Path.GetFileName(this.filename);
				for (int i = 0; i < level; i++)
				{
					a = this.filename.LastIndexOf(System.IO.Path.DirectorySeparatorChar, a-1);
					if (a < 0)
						return System.IO.Path.GetFileNameWithoutExtension(this.filename);
				}
				a++;

				int n = level+1;
				if (f.Length > 1)
				{
					if (int.TryParse(f[1], out n) == false)
						return System.IO.Path.GetFileNameWithoutExtension(this.filename);
				}

				if (n > level+1)
					return System.IO.Path.GetFileNameWithoutExtension(this.filename);
				if (n == level+1)
				{
					string ext = System.IO.Path.GetExtension(this.filename);
					return this.filename.Substring(a, this.filename.Length-a-ext.Length);
				}

				int b = a;
				for (int i = 0; i < n; i++)
				{
					b = this.filename.IndexOf(System.IO.Path.DirectorySeparatorChar, b+1);
					if (b < 0)
						return System.IO.Path.GetFileNameWithoutExtension(this.filename);
				}
				return this.filename.Substring(a, b-a);
			}
		}
	}
}